﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ProLinq.Projection.Visitors.Optimization
{
	/// <summary>
	/// Creates one big filter expression
	/// </summary>
	internal class FilterBuilderVisitor : ExpressionVisitor
	{
		#region Fields

		private Expression _logicalExpression;
		private ParameterExpression _currentParam;

		#endregion

		#region Properties

		public Expression ProcessedExpressionPart { get; set; }

		public LambdaExpression FilterExpression
		{
			get { return Expression.Lambda(this._logicalExpression, this._currentParam); }
		}

		#endregion

		#region Methods

		protected void CombineLogicalExpression(LambdaExpression lambda)
		{
			if (this._logicalExpression != null)
			{
				var logicalExpr = Expression.AndAlso(lambda.Body, this._logicalExpression);
				var replacer = new ParameterReplacer(this._currentParam, lambda.Parameters[0]);
				this._logicalExpression = replacer.Visit(logicalExpr);
				this._currentParam = lambda.Parameters[0];
			}
			else
			{
				this._logicalExpression = lambda.Body;
				this._currentParam = lambda.Parameters[0];
			}
		}

		protected override Expression VisitMethodCall(MethodCallExpression node)
		{
			MethodInfo methodInfo = node.Method;

			// Queryable extension methods
			if (methodInfo.IsGenericMethod && methodInfo.DeclaringType == typeof (Queryable))
			{
				if (methodInfo.Name == "Select")
				{
					var unary = (UnaryExpression) node.Arguments[1];
					var lambda = (LambdaExpression) unary.Operand;

					this.InvertSelect(lambda);
					this._currentParam = lambda.Parameters[0];
				}
				else
				{
					// search for a predicate
					if (node.Arguments.Count > 1)
					{
						var unary = node.Arguments[1] as UnaryExpression;
						if (unary != null)
						{
							var lambda = unary.Operand as LambdaExpression;
							if (lambda != null)
							{
								var logical = lambda.Body;
								if (logical.Type == typeof (Boolean))
								{
									this.CombineLogicalExpression(lambda);
								}
							}
						}
					}
				}
			}

			// process in reverse order to go throught functions from outer ti inner
			var args = new Expression[node.Arguments.Count];
			for (Int32 i = args.Length - 1; i >= 0; i--)
			{
				args[i] = this.Visit(node.Arguments[i]);
			}
			return node.Update(this.Visit(node.Object), args);
		}


		public override Expression Visit(Expression node)
		{
			if (node == this.ProcessedExpressionPart)
			{
				// return when processed part reached
				return this.ProcessedExpressionPart;
			}
			return base.Visit(node);
		}

		private void InvertSelect(LambdaExpression selectExpression)
		{
			var memberInitExpression = selectExpression.Body as MemberInitExpression;
			if (memberInitExpression != null)
			{
				for (Int32 i = 0; i < memberInitExpression.Bindings.Count; i++)
				{
					Expression arg = ((MemberAssignment) memberInitExpression.Bindings[i]).Expression;
					MemberInfo member = memberInitExpression.Bindings[i].Member;

					this._logicalExpression = new MemberReplacer(member, arg).Visit(this._logicalExpression);
				}
				return;
			}

			var newExpression = selectExpression.Body as NewExpression;
			if (newExpression != null)
			{
				for (Int32 i = 0; i < newExpression.Members.Count; i++)
				{
					Expression arg = newExpression.Arguments[i];
					MemberInfo member = newExpression.Members[i];

					this._logicalExpression = new MemberReplacer(member, arg).Visit(this._logicalExpression);
				}
				return;
			}

			var memberExpression = selectExpression.Body as MemberExpression;
			if (memberExpression != null)
			{
				this._logicalExpression =
					new ParameterByTypeReplacer(memberExpression.Type, memberExpression).Visit(this._logicalExpression);
			}
		}

		#endregion

		#region Nested

		internal class MemberReplacer : ExpressionVisitor
		{
			private readonly MemberInfo _memberToReplace;
			private readonly Expression _replacementExpression;

			public MemberReplacer(MemberInfo memberToReplace, Expression expr)
			{
				this._memberToReplace = memberToReplace;
				this._replacementExpression = expr;
			}

			protected override Expression VisitMember(MemberExpression node)
			{
				if (node.Member == this._memberToReplace)
					return this._replacementExpression;
				return base.VisitMember(node);
			}
		}

		internal class ParameterByTypeReplacer : ExpressionVisitor
		{
			private readonly Type _paramType;
			private readonly Expression _replacementExpression;

			public ParameterByTypeReplacer(Type paramType, Expression expr)
			{
				this._paramType = paramType;
				this._replacementExpression = expr;
			}

			protected override Expression VisitParameter(ParameterExpression node)
			{
				if (node.Type == this._paramType)
					return this._replacementExpression;
				return base.VisitParameter(node);
			}
		}

		internal class ParameterReplacer : ExpressionVisitor
		{
			private readonly ParameterExpression _param;
			private readonly Expression _replacementExpression;

			public ParameterReplacer(ParameterExpression param, Expression expr)
			{
				this._param = param;
				this._replacementExpression = expr;
			}

			protected override Expression VisitParameter(ParameterExpression node)
			{
				if (node == this._param)
					return this._replacementExpression;
				return base.VisitParameter(node);
			}
		}

		#endregion
	}
}
